/*
 * ProGuard -- shrinking, optimization, obfuscation, and preverification of Java bytecode.
 *
 * Copyright (c) 2002-2018 GuardSquare NV
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with this program; if not, write to the Free
 * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
package proguard.classfile.util;

import proguard.classfile.*;
import proguard.classfile.attribute.*;
import proguard.classfile.attribute.visitor.AttributeVisitor;
import proguard.classfile.constant.*;
import proguard.classfile.constant.visitor.ConstantVisitor;
import proguard.classfile.editor.*;
import proguard.classfile.instruction.*;
import proguard.classfile.instruction.visitor.InstructionVisitor;
import proguard.classfile.visitor.*;
import proguard.util.StringMatcher;

/**
 * This AttributeVisitor initializes any constant class member references of all code that it visits. It currently
 * handles invocations of Class#get[Declared]{Field,Constructor,Method} and
 * Atomic{Integer,Long,Reference}FieldUpdater.newUpdater with constant string arguments. It lets the corresponding
 * string constants refer to their class members in the program class pool or in the library class pool. It may create
 * new string constants and update the code, in order to avoid clashes between identically named class members.
 * <p>
 * The class hierarchy and references must be initialized before using this visitor.
 *
 * @see ClassSuperHierarchyInitializer
 * @see ClassReferenceInitializer
 *
 * @author Eric Lafortune
 */
public class DynamicMemberReferenceInitializer extends SimplifiedVisitor
    implements AttributeVisitor, InstructionVisitor, ConstantVisitor, MemberVisitor {
    /*
    private static       boolean DEBUG = true;
    /*/
    private static final boolean DEBUG = false;
    // */

    private static final int CLASS_INDEX = InstructionSequenceMatcher.A;
    private static final int MEMBER_NAME_INDEX = InstructionSequenceMatcher.B;
    private static final int MEMBER_TYPE_INDEX = InstructionSequenceMatcher.C;

    private final ClassPool programClassPool;
    private final ClassPool libraryClassPool;
    private final WarningPrinter notePrinter;
    private final StringMatcher noteFieldExceptionMatcher;
    private final StringMatcher noteMethodExceptionMatcher;

    // Retrieving fields or methods from known, constant classes.
    private final InstructionSequenceMatcher knownItegerUpdaterMatcher;
    private final InstructionSequenceMatcher knownLongUpdaterMatcher;
    private final InstructionSequenceMatcher knownReferenceUpdaterMatcher;

    // Retrieving fields or methods from unknown classes.
    private final InstructionSequenceMatcher unknownIntegerUpdaterMatcher;
    private final InstructionSequenceMatcher unknownLongUpdaterMatcher;
    private final InstructionSequenceMatcher unknownReferenceUpdaterMatcher;

    private final MyDynamicMemberFinder dynamicMemberFinder = new MyDynamicMemberFinder();

    private final MemberFinder memberFinder = new MemberFinder(true);
    private final MemberFinder declaredMemberFinder = new MemberFinder(false);
    private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor();

    // Fields acting as parameters for the visitors.
    private Clazz referencedClass;

    /**
     * Creates a new DynamicMemberReferenceInitializer.
     */
    public DynamicMemberReferenceInitializer(ClassPool programClassPool, ClassPool libraryClassPool,
        WarningPrinter notePrinter, StringMatcher noteFieldExceptionMatcher, StringMatcher noteMethodExceptionMatcher) {
        this.programClassPool = programClassPool;
        this.libraryClassPool = libraryClassPool;
        this.notePrinter = notePrinter;
        this.noteFieldExceptionMatcher = noteFieldExceptionMatcher;
        this.noteMethodExceptionMatcher = noteMethodExceptionMatcher;

        // Create the instruction sequences and matchers.
        InstructionSequenceBuilder builder = new InstructionSequenceBuilder(programClassPool, libraryClassPool);

        // AtomicIntegerFieldUpdater.newUpdater(A.class, "someField").
        Instruction[] knownItegerUpdaterInstructions =
            builder.ldc_(CLASS_INDEX).ldc_(MEMBER_NAME_INDEX)
                .invokestatic(ClassConstants.NAME_JAVA_UTIL_CONCURRENT_ATOMIC_ATOMIC_INTEGER_FIELD_UPDATER,
                    ClassConstants.METHOD_NAME_NEW_UPDATER, ClassConstants.METHOD_TYPE_NEW_INTEGER_UPDATER)
                .instructions();

        // AtomicLongFieldUpdater.newUpdater(A.class, "someField").
        Instruction[] knownLongUpdaterInstructions = builder.ldc_(CLASS_INDEX).ldc_(MEMBER_NAME_INDEX)
            .invokestatic(ClassConstants.NAME_JAVA_UTIL_CONCURRENT_ATOMIC_ATOMIC_LONG_FIELD_UPDATER,
                ClassConstants.METHOD_NAME_NEW_UPDATER, ClassConstants.METHOD_TYPE_NEW_LONG_UPDATER)
            .instructions();

        // AtomicReferenceFieldUpdater.newUpdater(A.class, B.class, "someField").
        Instruction[] knownReferenceUpdaterInstructions =
            builder.ldc_(CLASS_INDEX).ldc_(MEMBER_TYPE_INDEX).ldc_(MEMBER_NAME_INDEX)
                .invokestatic(ClassConstants.NAME_JAVA_UTIL_CONCURRENT_ATOMIC_ATOMIC_REFERENCE_FIELD_UPDATER,
                    ClassConstants.METHOD_NAME_NEW_UPDATER, ClassConstants.METHOD_TYPE_NEW_REFERENCE_UPDATER)
                .instructions();

        // AtomicIntegerFieldUpdater.newUpdater(..., "someField").
        Instruction[] unknownIntegerUpdaterInstructions =
            builder.ldc_(MEMBER_NAME_INDEX)
                .invokestatic(ClassConstants.NAME_JAVA_UTIL_CONCURRENT_ATOMIC_ATOMIC_INTEGER_FIELD_UPDATER,
                    ClassConstants.METHOD_NAME_NEW_UPDATER, ClassConstants.METHOD_TYPE_NEW_INTEGER_UPDATER)
                .instructions();

        // AtomicLongFieldUpdater.newUpdater(..., "someField").
        Instruction[] unknownLongUpdaterInstructions = builder.ldc_(MEMBER_NAME_INDEX)
            .invokestatic(ClassConstants.NAME_JAVA_UTIL_CONCURRENT_ATOMIC_ATOMIC_LONG_FIELD_UPDATER,
                ClassConstants.METHOD_NAME_NEW_UPDATER, ClassConstants.METHOD_TYPE_NEW_LONG_UPDATER)
            .instructions();

        // AtomicReferenceFieldUpdater.newUpdater(..., "someField").
        final Instruction[] unknownReferenceUpdaterInstructions = builder.ldc_(MEMBER_NAME_INDEX)
            .invokestatic(ClassConstants.NAME_JAVA_UTIL_CONCURRENT_ATOMIC_ATOMIC_REFERENCE_FIELD_UPDATER,
                ClassConstants.METHOD_NAME_NEW_UPDATER, ClassConstants.METHOD_TYPE_NEW_REFERENCE_UPDATER)
            .instructions();

        Constant[] constants = builder.constants();

        knownItegerUpdaterMatcher = new InstructionSequenceMatcher(constants, knownItegerUpdaterInstructions);
        knownLongUpdaterMatcher = new InstructionSequenceMatcher(constants, knownLongUpdaterInstructions);
        knownReferenceUpdaterMatcher = new InstructionSequenceMatcher(constants, knownReferenceUpdaterInstructions);
        unknownIntegerUpdaterMatcher = new InstructionSequenceMatcher(constants, unknownIntegerUpdaterInstructions);
        unknownLongUpdaterMatcher = new InstructionSequenceMatcher(constants, unknownLongUpdaterInstructions);
        unknownReferenceUpdaterMatcher = new InstructionSequenceMatcher(constants, unknownReferenceUpdaterInstructions);
    }

    // Implementations for AttributeVisitor.

    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}

    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) {
        if (DEBUG) {
            System.out.println("DynamicMemberReferenceInitializer: " + clazz.getName() + "." + method.getName(clazz)
                + method.getDescriptor(clazz));
        }

        // Set up the code attribute editor.
        codeAttributeEditor.reset(codeAttribute.u4codeLength);

        // Find the dynamic class member references.
        codeAttribute.instructionsAccept(clazz, method, this);

        // Apply any changes to the code.
        codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute);
    }

    // Implementations for InstructionVisitor.

    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset,
        Instruction instruction) {
        // Try to match get[Declared]{Field,Constructor,Method} constructs.
        instruction.accept(clazz, method, codeAttribute, offset, dynamicMemberFinder);

        // Try to match the AtomicIntegerFieldUpdater.newUpdater(
        // SomeClass.class, "someField") construct.
        matchGetMember(clazz, method, codeAttribute, offset, instruction, knownItegerUpdaterMatcher,
            unknownIntegerUpdaterMatcher, true, false, false, "" + ClassConstants.TYPE_INT);

        // Try to match the AtomicLongFieldUpdater.newUpdater(
        // SomeClass.class, "someField") construct.
        matchGetMember(clazz, method, codeAttribute, offset, instruction, knownLongUpdaterMatcher,
            unknownLongUpdaterMatcher, true, false, false, "" + ClassConstants.TYPE_LONG);

        // Try to match the AtomicReferenceFieldUpdater.newUpdater(
        // SomeClass.class, SomeClass.class, "someField") construct.
        matchGetMember(clazz, method, codeAttribute, offset, instruction, knownReferenceUpdaterMatcher,
            unknownReferenceUpdaterMatcher, true, false, false, null);
    }

    /**
     * Tries to match the next instruction and fills out the string constant or prints out a note accordingly.
     */
    private void matchGetMember(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset,
        Instruction instruction, InstructionSequenceMatcher constantSequenceMatcher,
        InstructionSequenceMatcher variableSequenceMatcher, boolean isField, boolean isConstructor, boolean isDeclared,
        String memberDescriptor) {
        if (constantSequenceMatcher != null) {
            // Try to match the next instruction in the constant sequence.
            instruction.accept(clazz, method, codeAttribute, offset, constantSequenceMatcher);

            // Did we find a match to fill out the string constant?
            if (constantSequenceMatcher.isMatching()) {
                // Retrieve the offset of the instruction that loads the member
                // name. It's currently always the last but one instruction.
                int memberNameInstructionOffset =
                    constantSequenceMatcher.matchedInstructionOffset(constantSequenceMatcher.instructionCount() - 2);

                // Get the member's class.
                int classIndex = constantSequenceMatcher.matchedConstantIndex(CLASS_INDEX);
                clazz.constantPoolEntryAccept(classIndex, this);

                if (referencedClass != null) {
                    // Get the field's type, if applicable.
                    int typeClassIndex = constantSequenceMatcher.matchedConstantIndex(MEMBER_TYPE_INDEX);
                    if (typeClassIndex > 0) {
                        memberDescriptor = ClassUtil.internalTypeFromClassName(clazz.getClassName(typeClassIndex));
                    }

                    // Get the member's name.
                    int memberNameIndex = constantSequenceMatcher.matchedConstantIndex(MEMBER_NAME_INDEX);
                    String memberName = clazz.getStringString(memberNameIndex);

                    // Create a new string constant and update the instruction.
                    initializeDynamicMemberReference(clazz, memberNameInstructionOffset, referencedClass, memberName,
                        memberDescriptor, isField, isConstructor, isDeclared);
                }

                // Don't look for the dynamic construct.
                variableSequenceMatcher.reset();
            }
        }

        // Try to match the next instruction in the variable sequence.
        instruction.accept(clazz, method, codeAttribute, offset, variableSequenceMatcher);

        // Did we find a match to print out a note?
        if (variableSequenceMatcher.isMatching()) {
            int memberNameIndex = variableSequenceMatcher.matchedConstantIndex(MEMBER_NAME_INDEX);
            String memberName = clazz.getStringString(memberNameIndex);

            // Print out a note about the dynamic invocation.
            printDynamicMemberAccessNote(clazz, memberName, memberDescriptor, isField, isConstructor, isDeclared);
        }
    }

    // Implementations for ConstantVisitor.

    public void visitClassConstant(Clazz clazz, ClassConstant classConstant) {
        // Remember the referenced class.
        referencedClass =
            ClassUtil.isInternalArrayType(classConstant.getName(clazz)) ? null : classConstant.referencedClass;
    }

    // Small utility methods.

    /**
     * Creates a new string constant for the specified referenced class member, and updates the instruction that loads
     * it.
     */
    private void initializeDynamicMemberReference(Clazz clazz, int memberNameInstructionOffset, Clazz referencedClass,
        String memberName, String memberDescriptor, boolean isField, boolean isConstructor, boolean isDeclared) {
        // See if we can find the referenced class member locally, or
        // somewhere in the hierarchy.
        MemberFinder referencedMemberFinder = isDeclared ? declaredMemberFinder : memberFinder;

        Member referencedMember =
            referencedMemberFinder.findMember(referencedClass, memberName, memberDescriptor, isField);

        if (DEBUG) {
            System.out.println("DynamicMemberReferenceInitializer: [" + clazz.getName() + "] matched string ["
                + memberName + "]: in [" + referencedClass + "] -> [" + referencedMember + "]");
        }

        if (referencedMember != null) {
            if (!isDeclared) {
                referencedClass = referencedMemberFinder.correspondingClass();
            }

            // Update the string constant.
            // stringConstant.referencedMember = referencedMember;
            // stringConstant.referencedClass = referencedClass;

            // Create a new string constant with the found references.
            int stringConstantIndex = new ConstantPoolEditor((ProgramClass)clazz).addStringConstant(memberName,
                referencedClass, referencedMember);

            // Update the instruction.
            codeAttributeEditor.replaceInstruction(memberNameInstructionOffset,
                new ConstantInstruction(InstructionConstants.OP_LDC, stringConstantIndex));
        }
    }

    /**
     * Prints out a note on the matched dynamic invocation of a constructor.
     */
    private void printDynamicConstructorAccessNote(Clazz clazz, Clazz referencedClass, String memberDescriptor,
        boolean isDeclared) {
        // Print out a note about the dynamic invocation.
        if (notePrinter != null && notePrinter.accepts(clazz.getName())) {
            // Is the class member name in the list of exceptions?
            if (noteMethodExceptionMatcher == null
                || !noteMethodExceptionMatcher.matches(ClassConstants.METHOD_NAME_INIT)) {
                // Print out the actual note.
                notePrinter.print(clazz.getName(),
                    "Note: " + ClassUtil.externalClassName(clazz.getName()) + " retrieves a "
                        + (isDeclared ? "declared " : "") + "constructor '" + ClassConstants.METHOD_NAME_INIT
                        + JavaConstants.METHOD_ARGUMENTS_OPEN + ClassUtil.externalMethodArguments(memberDescriptor)
                        + JavaConstants.METHOD_ARGUMENTS_CLOSE + "' dynamically");

                // Print out notes about potential candidates.
                ClassVisitor classVisitor = new AllMethodVisitor(new MemberNameFilter(ClassConstants.METHOD_NAME_INIT,
                    new MemberDescriptorFilter(memberDescriptor, this)));

                if (referencedClass != null) {
                    referencedClass.hierarchyAccept(true, !isDeclared, false, false, classVisitor);
                } else {
                    programClassPool.classesAcceptAlphabetically(classVisitor);
                    libraryClassPool.classesAcceptAlphabetically(classVisitor);
                }
            }
        }
    }

    /**
     * Prints out a note on the matched dynamic access to a class member.
     */
    private void printDynamicMemberAccessNote(Clazz clazz, String memberName, String memberDescriptor, boolean isField,
        boolean isConstructor, boolean isDeclared) {
        // Print out a note about the dynamic invocation.
        if (notePrinter != null && notePrinter.accepts(clazz.getName())) {
            // Is the class member name in the list of exceptions?
            StringMatcher noteExceptionMatcher = isField ? noteFieldExceptionMatcher : noteMethodExceptionMatcher;

            if (noteExceptionMatcher == null || !noteExceptionMatcher.matches(memberName)) {
                // Compose the external member name and partial descriptor.
                String externalMemberDescription = memberName;

                if (!isField) {
                    externalMemberDescription += JavaConstants.METHOD_ARGUMENTS_OPEN
                        + ClassUtil.externalMethodArguments(memberDescriptor) + JavaConstants.METHOD_ARGUMENTS_CLOSE;
                }

                // Print out the actual note.
                notePrinter.print(clazz.getName(),
                    "Note: " + ClassUtil.externalClassName(clazz.getName()) + " accesses a "
                        + (isDeclared ? "declared " : "")
                        + (isField ? "field" : isConstructor ? "constructor" : "method") + " '"
                        + externalMemberDescription + "' dynamically");

                // Print out notes about potential candidates.
                ClassVisitor classVisitor;

                if (isField) {
                    classVisitor = memberDescriptor == null
                        ? new AllFieldVisitor(new MemberNameFilter(memberName, this)) : new AllFieldVisitor(
                            new MemberNameFilter(memberName, new MemberDescriptorFilter(memberDescriptor, this)));
                } else {
                    classVisitor = new AllMethodVisitor(
                        new MemberNameFilter(memberName, new MemberDescriptorFilter(memberDescriptor, this)));
                }

                programClassPool.classesAcceptAlphabetically(classVisitor);
                libraryClassPool.classesAcceptAlphabetically(classVisitor);
            }
        }
    }

    // Implementations for MemberVisitor.

    public void visitProgramField(ProgramClass programClass, ProgramField programField) {
        if (notePrinter.accepts(programClass.getName())) {
            System.out.println("      Maybe this is program field '"
                + ClassUtil.externalFullClassDescription(0, programClass.getName()) + " { "
                + ClassUtil.externalFullFieldDescription(0, programField.getName(programClass),
                    programField.getDescriptor(programClass))
                + "; }'");
        }
    }

    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) {
        if (notePrinter.accepts(programClass.getName())) {
            System.out.println("      Maybe this is program method '"
                + ClassUtil.externalFullClassDescription(0, programClass.getName()) + " { "
                + ClassUtil.externalFullMethodDescription(programClass.getName(), 0,
                    programMethod.getName(programClass), programMethod.getDescriptor(programClass))
                + "; }'");
        }
    }

    public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField) {
        if (notePrinter.accepts(libraryClass.getName())) {
            System.out.println("      Maybe this is library field '"
                + ClassUtil.externalFullClassDescription(0, libraryClass.getName()) + " { "
                + ClassUtil.externalFullFieldDescription(0, libraryField.getName(libraryClass),
                    libraryField.getDescriptor(libraryClass))
                + "; }'");
        }
    }

    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) {
        if (notePrinter.accepts(libraryClass.getName())) {
            System.out.println("      Maybe this is library method '"
                + ClassUtil.externalFullClassDescription(0, libraryClass.getName()) + " { "
                + ClassUtil.externalFullMethodDescription(libraryClass.getName(), 0,
                    libraryMethod.getName(libraryClass), libraryMethod.getDescriptor(libraryClass))
                + "; }'");
        }
    }

    /**
     * This InstructionVisitor finds get[Declared]{Field,Constructor,Method} constructs with constant arguments. It then
     * makes sure the class member name strings point to the class members, or it prints out notes about the possible
     * alternatives.
     */
    private class MyDynamicMemberFinder extends SimplifiedVisitor implements InstructionVisitor, ConstantVisitor {
        private static final int LABEL_START = 0; // ldc SomeClass.class
        private static final int LABEL_LOAD_MEMBER_NAME = 1; // [ ldc "someMethod"/"someField" ]
        private static final int LABEL_LOAD_CLASS_ARRAY_SIZE = 2; // [ sipush #someParameterCount
        private static final int LABEL_CREATE_CLASS_ARRAY = 3; // anewarray java/lang/Class
        private static final int LABEL_DUP_CLASS_ARRAY = 4; // [ dup
        private static final int LABEL_LOAD_PARAMETER_INDEX = 5; // sipush #someParameterIndex
        private static final int LABEL_LOAD_PARAMETER_TYPE = 6; // ldc SomeParameterClass.class / getstatic
                                                                // java/lang/SomePrimitive.TYPE
        private static final int LABEL_STORE_PARAMETER = 7; // aastore ]* ] / aconst_null
        private static final int LABEL_GET_MEMBER = 8; // invokevirtual
                                                       // java/lang/Class.getField/getConstructor/getMethod

        private int label;
        private int instructionOffset;
        private int memberNameInstructionOffset;
        private Clazz referencedClass;
        private String memberName;
        private int parameterCount;
        private int parameterIndex;
        private StringBuffer parameterTypes = new StringBuffer();

        public void reset() {
            label = LABEL_START;
            referencedClass = null;
            memberName = null;
            parameterTypes.setLength(0);
        }

        // Implementations for InstructionVisitor.

        public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset,
            Instruction instruction) {
            if (DEBUG) {
                System.out.println("Label [" + label + "] A " + instruction.toString(offset));
            }

            reset();
        }

        public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset,
            SimpleInstruction simpleInstruction) {
            if (DEBUG) {
                System.out.println("Label [" + label + "] S " + simpleInstruction.toString(offset));
            }

            int transition = label | simpleInstruction.canonicalOpcode() << 8;

            switch (transition) {
                case LABEL_START | InstructionConstants.OP_ICONST_0 << 8:
                case LABEL_LOAD_MEMBER_NAME | InstructionConstants.OP_ICONST_0 << 8:
                case LABEL_CREATE_CLASS_ARRAY | InstructionConstants.OP_ICONST_0 << 8:
                case LABEL_DUP_CLASS_ARRAY | InstructionConstants.OP_ICONST_0 << 8:
                case LABEL_LOAD_PARAMETER_TYPE | InstructionConstants.OP_ICONST_0 << 8:
                case LABEL_STORE_PARAMETER | InstructionConstants.OP_ICONST_0 << 8:
                case LABEL_GET_MEMBER | InstructionConstants.OP_ICONST_0 << 8:
                    // This could be the start of creating a class array.
                    reset();
                    parameterCount = simpleInstruction.constant;
                    label = LABEL_CREATE_CLASS_ARRAY;
                    break;

                case LABEL_LOAD_CLASS_ARRAY_SIZE | InstructionConstants.OP_ICONST_0 << 8:
                    parameterCount = simpleInstruction.constant;
                    label = LABEL_CREATE_CLASS_ARRAY;
                    break;

                case LABEL_LOAD_CLASS_ARRAY_SIZE | InstructionConstants.OP_ACONST_NULL << 8:
                    parameterCount = 0;
                    label = LABEL_GET_MEMBER;
                    break;

                case LABEL_DUP_CLASS_ARRAY | InstructionConstants.OP_DUP << 8:
                    label = LABEL_LOAD_PARAMETER_INDEX;
                    break;

                case LABEL_LOAD_PARAMETER_INDEX | InstructionConstants.OP_ICONST_0 << 8:
                    // Is it pushing the expected parameter index?
                    if (parameterIndex == simpleInstruction.constant) {
                        label = LABEL_LOAD_PARAMETER_TYPE;
                    } else {
                        // This could be the start of creating a class array.
                        reset();
                        parameterCount = simpleInstruction.constant;
                        label = LABEL_CREATE_CLASS_ARRAY;
                    }
                    break;

                case LABEL_STORE_PARAMETER | InstructionConstants.OP_AASTORE << 8:
                    // Are we still expecting more parameters?
                    label = ++parameterIndex < parameterCount ? LABEL_DUP_CLASS_ARRAY : LABEL_GET_MEMBER;
                    break;

                default:
                    reset();
                    break;
            }
        }

        public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset,
            ConstantInstruction constantInstruction) {
            if (DEBUG) {
                System.out.println("Label [" + label + "] C " + constantInstruction.toString(offset));
            }

            // Let the constant figure out the transition.
            switch (constantInstruction.canonicalOpcode()) {
                case InstructionConstants.OP_LDC:
                    instructionOffset = offset;
                    clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
                    break;

                case InstructionConstants.OP_GETSTATIC:
                case InstructionConstants.OP_INVOKEVIRTUAL:
                    clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
                    break;

                case InstructionConstants.OP_ANEWARRAY:
                    if (label == LABEL_CREATE_CLASS_ARRAY) {
                        clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
                    } else {
                        reset();
                    }
                    break;

                default:
                    reset();
                    break;
            }
        }

        // Implementations for ConstantVisitor.

        public void visitAnyConstant(Clazz clazz, Constant constant) {
            reset();
        }

        public void visitClassConstant(Clazz clazz, ClassConstant classConstant) {
            // Argument of ldc or anewarray instruction.
            switch (label) {
                case LABEL_START:
                    referencedClass = classConstant.referencedClass;

                    label = LABEL_LOAD_MEMBER_NAME;
                    break;

                case LABEL_CREATE_CLASS_ARRAY:
                    if (classConstant.getName(clazz).equals(ClassConstants.NAME_JAVA_LANG_CLASS)) {
                        parameterIndex = 0;
                        label = parameterCount > 0 ? LABEL_DUP_CLASS_ARRAY : LABEL_GET_MEMBER;
                    } else {
                        referencedClass = classConstant.referencedClass;

                        label = LABEL_LOAD_MEMBER_NAME;
                    }
                    break;

                case LABEL_LOAD_PARAMETER_TYPE:
                    String parameterType = ClassUtil.internalTypeFromClassType(classConstant.getName(clazz));

                    parameterTypes.append(parameterType);

                    label = LABEL_STORE_PARAMETER;
                    break;

                default:
                    // For other states, we'll treat this as a potential
                    // initial class name.
                    referencedClass = classConstant.referencedClass;

                    label = LABEL_LOAD_MEMBER_NAME;
                    break;
            }
        }

        public void visitStringConstant(Clazz clazz, StringConstant stringConstant) {
            // Argument of ldc instruction.
            switch (label) {
                case LABEL_LOAD_MEMBER_NAME:
                    break;

                default:
                    // For other states, we'll treat this as a potential
                    // initial method name, without a known class.
                    referencedClass = null;
                    break;
            }

            // Whatever state, we'll treat this as a potential method name.
            memberNameInstructionOffset = instructionOffset;
            memberName = stringConstant.getString(clazz);

            label = LABEL_LOAD_CLASS_ARRAY_SIZE;
        }

        public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) {
            // Argument of getstatic instruction.
            switch (label) {
                case LABEL_LOAD_PARAMETER_TYPE:
                    String className = fieldrefConstant.getClassName(clazz);
                    String fieldName = fieldrefConstant.getName(clazz);
                    String fieldType = fieldrefConstant.getType(clazz);

                    if (className.startsWith(ClassConstants.PACKAGE_JAVA_LANG)
                        && fieldName.equals(ClassConstants.FIELD_NAME_TYPE)
                        && fieldType.equals(ClassConstants.FIELD_TYPE_TYPE)) {
                        char parameterType = ClassUtil.internalPrimitiveTypeFromNumericClassName(className);

                        parameterTypes.append(parameterType);

                        label = LABEL_STORE_PARAMETER;
                    } else {
                        reset();
                    }
                    break;

                default:
                    reset();
                    break;
            }
        }

        public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant) {
            // Argument of invokevirtual instruction.
            String className = methodrefConstant.getClassName(clazz);

            if (className.equals(ClassConstants.NAME_JAVA_LANG_CLASS)) {
                String methodName = methodrefConstant.getName(clazz);
                String methodType = methodrefConstant.getType(clazz);

                if (label == LABEL_LOAD_CLASS_ARRAY_SIZE
                    && methodType.equals(ClassConstants.METHOD_TYPE_CLASS_GET_FIELD) && memberName != null) {
                    if (methodName.equals(ClassConstants.METHOD_NAME_CLASS_GET_FIELD)) {
                        resolveMemberString(clazz, true, false, false);
                    } else if (methodName.equals(ClassConstants.METHOD_NAME_CLASS_GET_DECLARED_FIELD)) {
                        resolveMemberString(clazz, true, false, true);
                    } else {
                        reset();
                    }
                } else if (label == LABEL_GET_MEMBER
                    && methodType.equals(ClassConstants.METHOD_TYPE_CLASS_GET_CONSTRUCTOR)) {
                    if (methodName.equals(ClassConstants.METHOD_NAME_CLASS_GET_CONSTRUCTOR)) {
                        resolveMemberString(clazz, false, true, false);
                    } else if (methodName.equals(ClassConstants.METHOD_NAME_CLASS_GET_DECLARED_CONSTRUCTOR)) {
                        resolveMemberString(clazz, false, true, true);
                    } else {
                        reset();
                    }
                } else if (label == LABEL_GET_MEMBER && methodType.equals(ClassConstants.METHOD_TYPE_CLASS_GET_METHOD)
                    && memberName != null) {
                    if (methodName.equals(ClassConstants.METHOD_NAME_CLASS_GET_METHOD)) {
                        resolveMemberString(clazz, false, false, false);
                    } else if (methodName.equals(ClassConstants.METHOD_NAME_CLASS_GET_DECLARED_METHOD)) {
                        resolveMemberString(clazz, false, false, true);
                    } else {
                        reset();
                    }
                } else {
                    reset();
                }
            } else {
                reset();
            }
        }

        /**
         * Links the referenced class member in the string, or prints out notes about the possible alternatives.
         */
        private void resolveMemberString(Clazz clazz, boolean isField, boolean isConstructor, boolean isDeclared) {
            String memberDescriptor = isField ? null : ClassConstants.METHOD_ARGUMENTS_OPEN + parameterTypes.toString()
                + ClassConstants.METHOD_ARGUMENTS_CLOSE + "L***;";

            if (DEBUG) {
                System.out.println("DynamicMemberReferenceInitializer: found member access");
                System.out.println("  isField           = " + isField);
                System.out.println("  isConstructor     = " + isConstructor);
                System.out.println("  isDeclared        = " + isDeclared);
                System.out.println("  referenced class  = "
                    + (referencedClass == null ? "(none)" : "[" + referencedClass.getName() + "]"));
                System.out.println("  member name       = " + (memberName == null ? "(none)" : "[" + memberName + "]"));
                System.out.println(
                    "  member descriptor = " + (memberDescriptor == null ? "(none)" : "[" + memberDescriptor + "]"));
            }

            if (referencedClass != null) {
                if (isConstructor) {
                    // We currently can't fill out some reference to a
                    // constructor. Just print out notes instead.
                    printDynamicConstructorAccessNote(clazz, referencedClass, memberDescriptor, isDeclared);
                } else {
                    // Create a new string constant and update the instruction.
                    initializeDynamicMemberReference(clazz, memberNameInstructionOffset, referencedClass, memberName,
                        memberDescriptor, isField, isConstructor, isDeclared);
                }
            } else {
                // Print out notes about the method in some unknown class.
                printDynamicMemberAccessNote(clazz, isConstructor ? ClassConstants.METHOD_NAME_INIT : memberName,
                    memberDescriptor, isField, isConstructor, isDeclared);
            }
        }
    }
}